﻿using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using Jayden.Dll.Web.HtmlControls.Generics;

namespace Jayden.Dll.Web.Styles
{
	public class CssSelectorDescription : IComparable<CssSelectorDescription>
	{

		public enum ParameterType
		{
			Id,
			Class,
			Attribute,
			Value,
			Tag,
			Selector,
			OptionalSelector,
			Lang,
			Index
		}

		private string m_Format;
		private string m_Example;
		private string m_Description;
		private int m_CssVersion;
		private int m_Points;
		private ParameterType[] m_ParameterTypes;
		private string m_Pattern;
		private Regex m_Regex;

		public CssSelectorDescription(string format, string example, string description, int cssVersion, int points = 0, params ParameterType[] parameters)
		{
			m_Format = format;
#if DEBUG
			// do not store the example and description in release mode
			m_Example = example;
			m_Description = description;
#endif
			m_CssVersion = cssVersion;
			m_Points = points;
			m_ParameterTypes = parameters;

			List<string> patterns = new List<string>();
			string pattern = m_Format;
			int indexOf = pattern.IndexOf("{0}");
			if (indexOf != -1)
			{
				if (indexOf > 0)
					patterns.Add(Patternize(pattern.Substring(0, indexOf)));
				patterns.Add(GetNamedPattern(m_ParameterTypes[0], "p1"));
				int secondIndexOf = pattern.IndexOf("{1}", indexOf + 3);
				if (secondIndexOf != -1)
				{
					if (secondIndexOf > indexOf + 3)
						patterns.Add(Patternize(pattern.Substring(indexOf + 3, secondIndexOf - (indexOf + 3))));
					patterns.Add(GetNamedPattern(m_ParameterTypes[1], "p2"));
					indexOf = secondIndexOf;
					int thirdIndexOf = pattern.IndexOf("{2}", indexOf + 3);
					if (thirdIndexOf != -1)
					{
						if (thirdIndexOf > indexOf + 3)
							patterns.Add(Patternize(pattern.Substring(indexOf + 3, thirdIndexOf - (indexOf + 3))));
						patterns.Add(GetNamedPattern(m_ParameterTypes[2], "p3"));
						indexOf = thirdIndexOf;
					}
				}
				if (indexOf + 3 < pattern.Length)
					patterns.Add(Patternize(pattern.Substring(indexOf + 3)));
			}
			else
				patterns.Add(Patternize(pattern));

			m_Pattern = "^" + string.Concat(patterns) + "$";
			m_Regex = new Regex(m_Pattern, RegexOptions.Compiled);
		}

		public override string ToString() { return m_Format; }

		public string Format { get { return m_Format; } }
		public string Example { get { return m_Example; } }
		public string Description { get { return m_Description; } }
		public int CssVersion { get { return m_CssVersion; } }
		public int Points { get { return m_Points; } }
		public ParameterType[] ParameterTypes { get { return m_ParameterTypes; } }
		public string Pattern { get { return m_Pattern; } }
		public Regex Regex { get { return m_Regex; } }

		public static List<CssSelectorDescription> Parse(string cssSelector)
		{
			cssSelector = cssSelector.Trim();
			List<CssSelectorDescription> list = new List<CssSelectorDescription>();
			if (string.IsNullOrWhiteSpace(cssSelector))
			{
				list.Add(Empty);
				return list;
			}
			foreach (CssSelectorDescription d in m_Descriptions)
			{
				Match match = d.m_Regex.Match(cssSelector);
				if (match.Success)
				{
					list.Add(d);
					for (int c = 0; c < d.m_ParameterTypes.Length; c++)
					{
						if (d.m_ParameterTypes[c] == ParameterType.Selector || d.m_ParameterTypes[c] == ParameterType.OptionalSelector)
						{
							string subSelector = match.Groups[1 + c].Value;
							foreach (CssSelectorDescription sub in Parse(subSelector))
								list.Add(sub);
						}
					}
					return list;
				}
			}
			return list;
		}

		public static string Patternize(string text)
		{
			text = text

				.Replace("\\", "\\\\")
				.Replace("*", "\\*")
				.Replace("[", "\\[")
				.Replace("]", "\\]")
				.Replace(".", "\\.")
				.Replace("(", "\\(")
				.Replace(")", "\\)")
				.Replace("-", "\\-")
				.Replace("_", "\\_")
				.Replace("^", "\\^")
				.Replace("?", "\\?")
				.Replace("$", "\\$")
				.Replace("+", "\\+")
				.Replace(">", "\\>")
				.Replace("~", "\\~")
				.Replace("|", "\\|")
				.Replace(":", "\\:");
			if (string.IsNullOrWhiteSpace(text))
				return "[ ]+";
			//return "(" + text + "){1}";
			return text;
		}
		public static string GetNamedPattern(ParameterType type, string name)
		{
			//return string.Format("(?<{0}>?{1})", name, GetPattern(type));
			return string.Format("({1})", name, GetPattern(type));
		}
		public static string GetPattern(ParameterType type)
		{
			switch (type)
			{
				case ParameterType.Id:
					return "[a-zA-Z][a-zA-Z_\\-\\.0-9]*";
				case ParameterType.Class:
					return "[a-zA-Z][a-zA-Z_\\-\\.0-9]*";
				case ParameterType.Attribute:
					return "[a-zA-Z][a-zA-Z_\\-0-9]*";
				case ParameterType.Value:
					return ".*";
				case ParameterType.Tag:
					return HtmlControl.ElementTagPattern;
				case ParameterType.OptionalSelector:
					return ".*";
				case ParameterType.Selector:
					return ".+";
				case ParameterType.Lang:
					return "[a-zA-Z\\-]+";
				case ParameterType.Index:
					return "[0-9]+";
			}
			throw new InvalidOperationException();
		}

		public static IEnumerable<CssSelectorDescription> Descriptions { get { return m_Descriptions; } }
		private static readonly CssSelectorDescription Empty = new CssSelectorDescription(string.Empty, string.Empty, "Empty selector", 1);

		private static CssSelectorDescription[] m_Descriptions = new CssSelectorDescription[]
		{
			new CssSelectorDescription("*","*","Selects all elements",2, 0),
			new CssSelectorDescription("{0}","p","Selects all <p> elements",1, 1, ParameterType.Tag),
			new CssSelectorDescription("{0}.{1}",".intro","Selects all elements with class=\"intro\"",1, 10, ParameterType.OptionalSelector, ParameterType.Class),
			new CssSelectorDescription("{0}#{1}","#firstname","Selects the element with id=\"firstname\"",1, 100, ParameterType.OptionalSelector, ParameterType.Id),
			new CssSelectorDescription("{0} {1}","div p","Selects all <p> elements inside <div> elements",1, 0, ParameterType.Selector, ParameterType.Selector),
			new CssSelectorDescription("{0}>{1}","div>p","Selects all <p> elements where the parent is a <div> element",2, 0, ParameterType.Selector, ParameterType.Selector),
			new CssSelectorDescription("{0}+{1}","div+p","Selects all <p> elements that are placed immediately after <div> elements",2, 0, ParameterType.Selector, ParameterType.Selector),
			new CssSelectorDescription("{0}~{1}","p~ul","Selects every <ul> element that are preceded by a <p> element",3, 0, ParameterType.Selector, ParameterType.Selector),
			new CssSelectorDescription("{0}:not({1})",":not(p)","Selects every element that is not a <p> element",3, 10, ParameterType.OptionalSelector, ParameterType.Selector),
			new CssSelectorDescription("{0}[{1}]","[target]","Selects all elements with a target attribute",2, 10, ParameterType.OptionalSelector, ParameterType.Attribute),
			new CssSelectorDescription("{0}[{1}={2}]","[target=_blank]","Selects all elements with target=\"_blank\"",2, 10, ParameterType.OptionalSelector, ParameterType.Attribute, ParameterType.Value),
			new CssSelectorDescription("{0}[{1}~={2}]","[title~=flower]","Selects all elements with a title attribute containing the word \"flower\"",2, 10, ParameterType.OptionalSelector, ParameterType.Attribute, ParameterType.Value),
			new CssSelectorDescription("{0}[{1}|={2}]","[lang|=en]","Selects all elements with a lang attribute value starting with \"en\"",2, 10, ParameterType.OptionalSelector, ParameterType.Attribute, ParameterType.Value),
			new CssSelectorDescription("{0}:link","a:link","Selects all unvisited links",1, 10, ParameterType.OptionalSelector),
			new CssSelectorDescription("{0}:visited","a:visited","Selects all visited links",1, 10, ParameterType.OptionalSelector),
			new CssSelectorDescription("{0}:active","a:active","Selects the active link",1, 10, ParameterType.OptionalSelector),
			new CssSelectorDescription("{0}:hover","a:hover","Selects links on mouse over",1, 10, ParameterType.OptionalSelector),
			new CssSelectorDescription("{0}:focus","input:focus","Selects the input element which has focus",2, 10, ParameterType.OptionalSelector),
			new CssSelectorDescription("{0}:first-letter","p:first-letter","Selects the first letter of every <p> element",1, 10, ParameterType.OptionalSelector),
			new CssSelectorDescription("{0}:first-line","p:first-line","Selects the first line of every <p> element",1, 10, ParameterType.OptionalSelector),
			new CssSelectorDescription("{0}:first-child","p:first-child","Selects every <p> element that is the first child of its parent",2, 10, ParameterType.OptionalSelector),
			new CssSelectorDescription("{0}:before","p:before","Insert content before  the content of every <p> element",2, 10, ParameterType.OptionalSelector),
			new CssSelectorDescription("{0}:after","p:after","Insert content after every <p> element",2, 10, ParameterType.OptionalSelector),
			new CssSelectorDescription("{0}:lang({1})","p:lang(it)","Selects every <p> element with a lang attribute value starting with \"it\"",2, 10, ParameterType.OptionalSelector, ParameterType.Lang),
			new CssSelectorDescription("{0}[{1}^={2}]","a[src^=\"https\"]","Selects every <a> element whose src attribute value begins with \"https\"",3, 10, ParameterType.OptionalSelector, ParameterType.Attribute, ParameterType.Value),
			new CssSelectorDescription("{0}[{1}$={2}]","a[src$=\".pdf\"]","Selects every <a> element whose src attribute value ends with \".pdf\"",3, 10, ParameterType.OptionalSelector, ParameterType.Attribute, ParameterType.Value),
			new CssSelectorDescription("{0}[{1}*={2}]","a[src*=\"w3schools\"]","Selects every <a> element whose src attribute value contains the substring \"w3schools\"",3, 10, ParameterType.OptionalSelector, ParameterType.Attribute, ParameterType.Value),
			new CssSelectorDescription("{0}:first-of-type","p:first-of-type","Selects every <p> element that is the first <p> element of its parent",3, 10, ParameterType.OptionalSelector),
			new CssSelectorDescription("{0}:last-of-type","p:last-of-type","Selects every <p> element that is the last <p> element of its parent",3, 10, ParameterType.OptionalSelector),
			new CssSelectorDescription("{0}:only-of-type","p:only-of-type","Selects every <p> element that is the only <p> element of its parent",3, 10, ParameterType.OptionalSelector),
			new CssSelectorDescription("{0}:only-child","p:only-child","Selects every <p> element that is the only child of its parent",3, 10, ParameterType.OptionalSelector),
			new CssSelectorDescription("{0}:nth-child({1})","p:nth-child(2)","Selects every <p> element that is the second child of its parent",3, 10, ParameterType.OptionalSelector, ParameterType.Index),
			new CssSelectorDescription("{0}:nth-last-child({1})","p:nth-last-child(2)","Selects every <p> element that is the second child of its parent, counting from the last child",3, 10, ParameterType.OptionalSelector, ParameterType.Index),
			new CssSelectorDescription("{0}:nth-of-type({1})","p:nth-of-type(2)","Selects every <p> element that is the second <p> element of its parent",3, 10, ParameterType.OptionalSelector, ParameterType.Index),
			new CssSelectorDescription("{0}:nth-last-of-type({1})","p:nth-last-of-type(2)","Selects every <p> element that is the second <p> element of its parent, counting from the last child",3, 10, ParameterType.OptionalSelector, ParameterType.Index),
			new CssSelectorDescription("{0}:last-child","p:last-child","Selects every <p> element that is the last child of its parent",3, 10, ParameterType.OptionalSelector),
			new CssSelectorDescription("{0}:root",":root","Selects the document’s root element",3, 10, ParameterType.OptionalSelector),
			new CssSelectorDescription("{0}:empty","p:empty","Selects every <p> element that has no children (including text nodes)",3, 10, ParameterType.OptionalSelector),
			new CssSelectorDescription("{0}:target","#news:target","Selects the current active #news element (clicked on a URL containing that anchor name)",3, 10, ParameterType.OptionalSelector),
			new CssSelectorDescription("{0}:enabled","input:enabled","Selects every enabled <input> element",3, 10, ParameterType.OptionalSelector),
			new CssSelectorDescription("{0}:disabled","input:disabled","Selects every disabled <input> element",3, 10, ParameterType.OptionalSelector),
			new CssSelectorDescription("{0}:checked","input:checked","Selects every checked <input> element",3, 10, ParameterType.OptionalSelector),
			new CssSelectorDescription("{0}::selection","::selection","Selects the portion of an element that is selected by a user",3, 10, ParameterType.OptionalSelector)
		};

		public int CompareTo(CssSelectorDescription other)
		{
			int compare;
			compare = Points.CompareTo(other.Points);
			if (compare != 0)
				return compare;
			compare = CssVersion.CompareTo(other.CssVersion);
			if (compare != 0)
				return compare;
			return Format.CompareTo(other.Format);
		}
	}
}
